<?php
/*
    Id:     $Id: $
    File:   Validator.php
    Author: John Griffin
    Date:   May 16, 2009

    Description:
*/
require_once 'lib/Error.php';

class Validator {
    private $resource = null;
    private $error = null;
    
    public function __construct (SimpleXMLElement $resource, Error $error) {
        $this->resource = $resource;
        $this->error = $error;
    }
        
    private function validateValue ($field, $value) {
        $return = false;
        switch ($field['type']) {
            case 'int':
                $return = (preg_match ('/^-{0,1}\d+$/', $value)) ? true : false;
                break;
            case 'date':
                $return = (preg_match ('/^(19|20)[0-9]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/', $value)) ? true : false;
                break;
            case 'string':
                $return = true;     // need test for sql injection (can I test at this point?)
            case 'real':
                $return = (preg_match ('/[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?/', $value)) ? true : false;
            default:
                $this->error->addError (Error::BAD_TYPE, $field['name'], "Bad data type ${field['type']}");
        }
        return $return;
    }

    public function validatePostRecord (array $record) {
        foreach ($this->resource->field as $field) {
            if (!($field['required'] == 'true' && (isset ($record[$field['name']]) == false || $record[$field['name']] == ''))) {
                $this->error->addError (Error::REQUIRED_FIELD_MISSING, $field['name'], 'Missing Required Field: '.$field['name']);
            } else {
                if (isset ($record[$field['name']]) == true) {
                    $valid = $this->validateValue ($value, $record[$field['name']]);
                    if ($valid == false) {
                        $this->error->addError (Error::BAD_VALUE, $field['name'], "Invalid value '${record[$field['name']]}' for field ${field['name']}.");
                    }
                }
            }
        }
    }

    public function validatePutRecord (array $record) {
        $unique_key = $this->resource->uniqueKey;
        $unique_key_found = false;
        foreach ($this->resource->field as $field) {
            if (isset ($record[$field['name']]) == true) {
                if ($record[$field['name']] == $unique_key) {
                    $unique_key_found = true;
                }
                $valid = $this->validateValue ($value, $record[$field['name']]);
                if ($valid == false) {
                    $this->error->addError (Error::BAD_VALUE, $field['name'], "Invalid value '${record[$field['name']]}' for field ${field['name']}.");
                }
            }
        }
        if ($unique_key_found == false) {
            $this->error->addError (Error::REQUIRED_FIELD_MISSING, $unique_key, 'Missing Unique Key: '.$unique_key);
        }
    }
    
    private function parseQueryInt ($name, $value) {
        $return = false;
        if (preg_match ('/^-{0,1}\d+$/', $value)                                              // integer or -integer
            || (preg_match ('/^\[-{0,1}\d+:]$/', $value))                                    // [n:]
            || (preg_match ('/^\[:-{0,1}\d+]$/', $value))                                    // [:m]
            || (preg_match ('/^\[-{0,1}\d+:-{0,1}\d+]$/', $value))                            // [n:m]
            || (preg_match ('/^\{(-{0,1}\d+,*[[:space:]]*)*}$/', $value))) {            // {a,b,c,d}
            $return = true;
        } else {
            $this->error->addError (Error::BAD_INTEGER_OR_EXPRESSION, $name, "Bad integer or integer expression for field '$name': $value");
        }
        return $return;
    }
    
    private function parseQueryReal ($name, $value) {
        $return = false;
        if (preg_match ('/[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?/', $value)                                              // real or -real
            || (preg_match ('/^\[[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?:]$/', $value))                                    // [n:]
            || (preg_match ('/^\[:[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?]$/', $value))                                    // [:m]
            || (preg_match ('/^\[[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?:[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?]$/', $value))                            // [n:m]
            || (preg_match ('/^\{([+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?,*[[:space:]]*)*}$/', $value))) {            // {a,b,c,d}
            $return = true;
        } else {
            $this->error->addError (Error::BAD_REAL_OR_EXPRESSION, $name, "Bad integer or integer expression for field '$name': $value");
        }
        return $return;
    }

    private function parseQueryDate ($name, $value) {
        $return = '';
        if (preg_match ('/^(19|20)[0-9]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/', $value)) {                                             // YYYY-MM-DD
            $return = $name." = '".$value."'";
        } elseif (preg_match ('/^\[(1[0-9]|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]):]$/', $value)) {                                   // [n:]
            $return = $name." >= '".substr ($value, 1,  -2)."'";
        } elseif (preg_match ('/^\[:(1[0-9]|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])]$/', $value)) {                                   // [:m]
            $return = $name." <= '".substr ($value, 2, -1)."'";
        } elseif (preg_match ('/^\[(1[0-9]|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]):(1[0-9]|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])]$/', $value)) {                           // [n:m]
            $return = $name." BETWEEN '".str_replace (':', "' AND '", substr ($value, 1, -1))."'";
        } elseif (preg_match ('/^\{((1[0-9]|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]),*[[:space:]]*)+}$/', $value)) {            // {a,b,c,d}
            $return = $name." IN ('".preg_replace ('/,[[:space:]]*/', "', '", substr ($value, 1, -1))."')";
        } else {
            $this->error->addError (Error::BAD_DATE_OR_EXPRESSION, $name, "Bad date or date expression for field '$name': $value");
        }
        return $return;
    }
    
    private function parseQueryString ($name, $value) {
        // there isn't really anything to do here
        return $value;
    }
    
    private function parseQueryFilter (array $fields, array $filter) {
        static $parse = array ('int' => 'parseQueryInt', 'date' => 'parseQueryDate', 'real' => 'parseQueryReal', 'string' => 'parseQueryString');
        $return = array ();
        foreach ($fields as $key => $value) {
            if (isset ($filter[$key]) == true) {
                assert (isset ($parse[$value]) == true);
                $return[] = $this->$parse[$value] ($key, $filter[$key]);
            }
        }
        return $return;
    }

    private function parseQueryModifiers (array $modifiers) {
        $count = 10;
        $start = 0;
        $return = array ();
        if (isset ($modifiers['count']) == true) {
            if (preg_match ('/^\d+$/', $modifiers['count'])) {
                $count = $modifiers['count'];
            } else {
                $this->error->addError (Error::BAD_INTEGER_OR_EXPRESSION, 'count', "Bad integer or integer expression for field 'count': $value");
            }
            $return['count'] = $modifiers['count'];
        }
        if (isset ($modifiers['start']) == true) {
            if (preg_match ('/^\d+$/', $modifiers['start'])) {
                $start = $modifiers['start'];
            } else {
                $this->error->addError (Error::BAD_INTEGER_OR_EXPRESSION, 'start', "Bad integer or integer expression for field 'start': $value");
            }
            $return['start'] = $modifiers['start'];
        }
        if (isset ($modifiers['order']) == true) {
            $this->error->addError (Error::NOT_IMPLEMENTED, 'order', "order modifier not yet implemeted");
        }
        if (isset ($modifiers['response']) == true) {
            $this->error->addError (Error::NOT_IMPLEMENTED, 'response', "response modifier not yet implemeted");
        }
        return $return;
    }
    
    public function validateQueryFilter (array $fields, array $modifiers = array ()) {
        $filter_return = $this->parseQueryFilter ($fields, $filter);
        $modifier_return = $this->parseQueryModifiers ($modifiers);
        $return = array_merge ($filter_return, $modifier_return);
        return $return;
    }
    
    public function validateIds (array $ids) {
        $bad_values = array ();
        foreach ($ids as $value) {
            if (preg_match ('/^[+-]?\d+$/', $value) == 0) {
                $bad_values[] = $value;
            }
        }
        if (count ($bad_values) > 0) {
            $this->error->addError (Error::BAD_IDS, '', 'Invalid ids for delete operation: '.implode (', ', $bad_values));
        }
    }
}
?>
